﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024 LiLong                                                   **
**  This file is part of GnssLogs.                                              **
**                                                                              **
**  GnssLogs is free software: you can redistribute it and/or modify            **
**  it under the terms of the GNU Lesser General Public License as published by **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  GnssLogs is distributed in the hope that it will be useful,                 **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU Lesser General Public License for more details.                         **
**                                                                              **
**  You should have received a copy of the GNU Lesser General Public License    **
**  along with GnssLogs.  If not, see <https://www.gnu.org/licenses/>.          **
**********************************************************************************/
#pragma once

#include "Flags.h"

#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/pfr.hpp>

#include <array>
#include <format>
#include <map>
#include <optional>
#include <vector>

#define GNSSLOGS_DECLARE_FORMATTER(Type)                                                                                                   \
    template<class CharT>                                                                                                                  \
    struct std::formatter<Type, CharT>                                                                                                     \
    {                                                                                                                                      \
        constexpr typename std::basic_format_parse_context<CharT>::iterator parse(std::basic_format_parse_context<CharT>& pc)              \
        {                                                                                                                                  \
            std::basic_string<CharT> str(pc.begin(), pc.end());                                                                            \
            if (pc.begin() == pc.end() || *pc.begin() == '}')                                                                              \
            {                                                                                                                              \
                return pc.end();                                                                                                           \
            }                                                                                                                              \
            else                                                                                                                           \
            {                                                                                                                              \
                throw std::format_error(std::format("Invalid format arg {}", #Type));                                                      \
            }                                                                                                                              \
        }                                                                                                                                  \
                                                                                                                                           \
        template<typename OutIterator>                                                                                                     \
        auto format(const Type& t, std::basic_format_context<OutIterator, CharT>& fc) const                                                \
        {                                                                                                                                  \
            constexpr static auto names = boost::pfr::names_as_array<Type>();                                                              \
            std::format_to(fc.out(), "{} : \n", #Type);                                                                                    \
            boost::pfr::for_each_field(t,                                                                                                  \
                                       [&](const auto& field, std::size_t idx)                                                             \
                                       {                                                                                                   \
                                           std::format_to(fc.out(), "{}", names[idx]);                                                     \
                                           std::format_to(fc.out(), " : ");                                                                \
                                           if constexpr (std::is_floating_point_v<std::decay_t<decltype(field)>>)                          \
                                               std::format_to(fc.out(), "{}\n", std::format("{:e}", field));                               \
                                           else                                                                                            \
                                               std::format_to(fc.out(), "{}\n", std::format("{}", field));                                 \
                                       });                                                                                                 \
            return fc.out();                                                                                                               \
        }                                                                                                                                  \
    };

#define GNSSLOGS_ENUM_DECLARE_FORMATTER(NS, Enum, ...)                                                                                     \
    namespace NS                                                                                                                           \
    {                                                                                                                                      \
    BOOST_DESCRIBE_ENUM(NS::Enum, ##__VA_ARGS__)                                                                                           \
    }                                                                                                                                      \
    template<class CharT>                                                                                                                  \
    struct std::formatter<NS::Enum, CharT>                                                                                                 \
    {                                                                                                                                      \
        constexpr typename std::basic_format_parse_context<CharT>::iterator parse(std::basic_format_parse_context<CharT>& pc)              \
        {                                                                                                                                  \
            std::basic_string<CharT> str(pc.begin(), pc.end());                                                                            \
            if (pc.begin() == pc.end() || *pc.begin() == '}')                                                                              \
            {                                                                                                                              \
                return pc.end();                                                                                                           \
            }                                                                                                                              \
            else                                                                                                                           \
            {                                                                                                                              \
                throw std::format_error(std::format("Invalid format arg {}", #Enum));                                                      \
            }                                                                                                                              \
        }                                                                                                                                  \
                                                                                                                                           \
        template<typename OutIterator>                                                                                                     \
        auto format(NS::Enum t, std::basic_format_context<OutIterator, CharT>& fc) const                                                   \
        {                                                                                                                                  \
            std::format_to(fc.out(), "{}", boost::describe::enum_to_string(t, "unknown"));                                                 \
            return fc.out();                                                                                                               \
        }                                                                                                                                  \
    };

template<class T, class CharT>
struct std::formatter<std::vector<T>, CharT> : std::formatter<T, CharT>
{
    template<class FormatContext>
    auto format(const std::vector<T>& t, FormatContext& fc) const
    {
        std::format_to(fc.out(), "[");
        for (auto it = t.begin(); it != t.end(); ++it)
        {
            if (it + 1 != t.end())
                std::format_to(std::formatter<T, CharT>::format(*it, fc), ",");
            else
                std::formatter<T, CharT>::format(*it, fc);
        }
        return std::format_to(fc.out(), "]");
    }
};

template<class T, size_t size, class CharT>
struct std::formatter<std::array<T, size>, CharT> : std::formatter<T, CharT>
{
    template<class FormatContext>
    auto format(const std::array<T, size>& t, FormatContext& fc) const
    {
        std::format_to(fc.out(), "[");
        for (auto it = t.begin(); it != t.end(); ++it)
        {
            if (it + 1 != t.end())
                std::format_to(std::formatter<T, CharT>::format(*it, fc), ",");
            else
                std::formatter<T, CharT>::format(*it, fc);
        }
        return std::format_to(fc.out(), "]");
    }
};

template<class T, class CharT>
struct std::formatter<std::optional<T>, CharT> : std::formatter<T, CharT>
{
    template<class FormatContext>
    auto format(const std::optional<T>& t, FormatContext& fc) const
    {
        if (t)
            return std::formatter<T, CharT>::format(*t, fc);
        else
            std::format_to(fc.out(), "null");
        return fc.out();
    }
};

template<class T, class CharT>
struct std::formatter<GnssLogs::Flags<T>, CharT> : std::formatter<T, CharT>
{
    template<class FormatContext>
    auto format(const GnssLogs::Flags<T>& t, FormatContext& fc) const
    {
        bool first = true;
        boost::mp11::mp_for_each<boost::describe::describe_enumerators<T>>(
            [&](auto d)
            {
                if (t.testFlag(d.value))
                {
                    if (first)
                        std::format_to(fc.out(), "{}", d.value);
                    else
                        std::format_to(fc.out(), ", {}", d.value);
                    first = false;
                }
            });
        return fc.out();
    }
};